﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PCRL = Pws.Clients.RestLibrary;
using PCRLS = Pws.Clients.RestLibrary.Shared;
using PCRLE = Pws.Clients.RestLibrary.ECommerce;
using PCRLC = Pws.Clients.RestLibrary.Customers;
using PCRLP = Pws.Clients.RestLibrary.Products;
using System.IO;
using System.Diagnostics;

namespace Pws.Clients.RestLibrary.Service
{
	/*
	 * Copyright (C) PWS Distributors Ltd 2011-2013
	 * All rights reserved.
	 * 
	 */

	/// <summary>
	/// An implementation for abstracting the PWS REST service calls into a higher level concept, using IPwsObjectWrapper for performing
	/// operations on PWS REST Objects.
	/// 
	/// Client programs written using .Net should use this as their primary API for PWS REST Web Services.
	/// 
	/// Calls to this code can catch an exception derived from PwsServiceResponseException:
	/// PwsServiceConflictException,
	/// PwsServiceAuthenticationException,
	/// PwsServicePermissionException,
	/// PwsServiceValidationException(we)
	/// PwsServiceNullRelationshipException,
	/// 
	/// Instances and operations are thread safe.
	/// </summary>
	public class PwsApplication : IPwsApplication
	{
		/// <summary>
		/// The service interface is passed in during construction.
		/// If the configuration changes, then instantiate a new PwsApplication with a new IPwsService object.
		/// </summary>
		private readonly IPwsService _service;

		/// <summary>
		/// This will be assigned implicitly using the default credentials; or explicitly by SetDefaultToken being called.
		/// </summary>
		private PCRLE.Users.Token_V1 _defaultToken;

		/// <summary>
		/// Tuple used when attempting to get a new Token.
		/// </summary>
		private KeyValuePair<String, String> _defaultCredentials;

		/// <summary>
		/// If the _defaultToken is being used, then this value is the minimum time for which the token must be valid
		/// until it is automatically renewed.
		/// </summary>
		private static TimeSpan __defaultTokenRenewTokenIfExpiring = TimeSpan.FromHours(1);

		/// <summary>
		/// How long a Token's expiry should be requested for.
		/// </summary>
		private static TimeSpan __defaultTokenExpiryTime = TimeSpan.FromHours(4);

		/// <summary>
		/// Instantiate a new implementation of the IPwsApplication high level wrapper for the PWS REST Web Services.
		/// Requires an IPwsService interface object, which provides the RESTful method implementation for Get, Put, etc.
		/// This overload takes no credentials, so if any REST interaction requires a Token (e.g. GetDefaultToken) then the credentials must be assigned explicitly by calling SetDefaultToken or SetDefaultCredentials;
		/// or consider using the constructor overload which takes a username and password instead.
		/// </summary>
		/// <param name="service">An IPwsService implementation for REST Get, Put, etc.</param>
		public PwsApplication(IPwsService service)
		{
			if (service == null)
				throw new ArgumentNullException("service", "The PwsApplication must be instantiated with an IPwsService object");

			_service = service;
		}

		/// <summary>
		/// Instantiate a new implementation of the IPwsApplication high level wrapper for the PWS REST Web Services.
		/// Requires an IPwsService interface object, which provides the RESTful method implementation for Get, Put, etc.
		/// This overload takes non-null credentials which will be used for creating a default token, so that GetDefaultToken can be used immediately.
		/// </summary>
		/// <param name="service">An IPwsService implementation for REST Get, Put, etc.</param>
		/// <param name="username">Username credential.</param>
		/// <param name="password">Password credential.</param>
		public PwsApplication(IPwsService service, String username, String password)
			: this(service)
		{
			SetDefaultCredentials(username, password);
		}

		/// <summary>
		/// Instantiate a new implementation of the IPwsApplication high level wrapper for the PWS REST Web Services.
		/// Requires an IPwsService interface object, which provides the RESTful method implementation for Get, Put, etc.
		/// This overload takes a non-null default PWS Token object, so that GetDefaultToken can be used immediately.
		/// </summary>
		/// <param name="service">An IPwsService implementation for REST Get, Put, etc.</param>
		/// <param name="token">A non-null user Token_V1.</param>
		public PwsApplication(IPwsService service, PCRLE.Users.Token_V1 token)
			: this(service)
		{
			SetDefaultToken(token);
		}

		/// <summary>
		/// Instantiate a new implementation of the IPwsApplication high level wrapper for the PWS REST Web Services.
		/// Requires an IPwsService interface object, which provides the RESTful method implementation for Get, Put, etc.
		/// This overload takes a non-null default PWS Token object, so that GetDefaultToken can be used immediately.
		/// </summary>
		/// <param name="service">An IPwsService implementation for REST Get, Put, etc.</param>
		/// <param name="serialisedToken">A non-null user Token_V1 which has previously been serialised.</param>
		public PwsApplication(IPwsService service, String serialisedToken)
			: this(service)
		{
			SetDefaultToken(new PCRLE.Users.Token_V1().FromSerialised(serialisedToken));
		}

		#region Advanced operations and primarily internal infrastructure. Most Clients will not need these.
		/// <summary>
		/// The IPwsService in use by this IPwsApplication instance.
		/// This provides the underlying mechanism for transmission using the REST interface.
		/// </summary>
		public IPwsService PwsService
		{
			get { return _service; }
		}

		/// <summary>
		/// Get the root services object, which defines the main services available.
		/// Most Clients will use GetDefaultToken instead, bypassing GetRootServices.
		/// </summary>
		/// <returns>Services_V1 object.</returns>
		public Services_V1 GetRootServices()
		{
			return _service.GetRoot<Services_V1>();
		}

		/// <summary>
		/// Most Clients will use GetDefaultToken instead, bypassing GetRootService.
		/// </summary>
		/// <param name="identifier">The fixed identifier name for the root service. Examples is productstore.</param>
		/// <typeparam name="T">Object type to return, e.g. ProductStore_V1</typeparam>
		/// <returns>Main object for the service in question.</returns>
		public IPwsObjectWrapper<T> GetRootService<T>(String identifier) where T : class, IPwsObject_V1
		{
			// NB this only works for root services that return a single IPwsObject_V1 instance, i.e. not tokenlist or passwordlist.
			// This is acceptable because most Clients will never use tokenlist or passwordlist directly anyway.

			Link_V1 link = GetRootServices().FindLink(identifier);
			T root = _service.Get<T>(link.Href);
			return Wrap<T>(root);
		}

		/// <summary>
		/// Post to a root services object by identifier.
		/// Generally for PWS use only; client applications will use GetDefaultToken.
		/// </summary>
		/// <param name="identifier">The fixed identifier name for the root service. Example is feedbacklist.</param>
		/// <param name="pwsObject">The PWS Object.</param>
		/// <typeparam name="T">Object type to return, e.g. Feedback_V1</typeparam>
		/// <returns>Posted object from the service in question.</returns>
		public IPwsObjectWrapper<T> PostRootService<T>(String identifier, T pwsObject) where T : class, IPwsObject_V1
		{
			Link_V1 link = GetRootServices().FindLink(identifier);
			T result = _service.Post<T>(link.Href, pwsObject, GetDefaultTokenUnwrapped(true));
			return Wrap<T>(result);
		}
		#endregion

		#region Obtaining Tokens. Most Clients will start with this. A Token is required when the user will interact with private data, or affect a change to some data.
		/// <summary>
		/// Set or change the default credentials which are used as the fallback for GetDefaultToken().
		/// </summary>
		/// <param name="username">Username credential.</param>
		/// <param name="password">Password credential.</param>
		public void SetDefaultCredentials(String username, String password)
		{
			if (username == null)
				throw new ArgumentNullException("username", "A username must be supplied so that a default Token can be obtained");

			if (password == null)
				throw new ArgumentNullException("password", "A password must be supplied so that a default Token can be obtained");

			_defaultCredentials = new KeyValuePair<string, string>(username, password);
			_defaultToken = null;
		}

		/// <summary>
		/// Gets the default Token_V1 object, wrapped into a helper class. The Token is the starting point for most REST operations as it contains
		/// a link to several operations within the context of the user login.
		/// 
		/// The Token will be obtained by using the default credentials (if they have been set), or else by SetDefaultToken being called.
		/// If the Token is due to expire soon, then an attempt will be made to renew it.
		/// </summary>
		public IPwsObjectWrapperSerialisable<PCRLE.Users.Token_V1> GetDefaultToken()
		{
			return WrapSerialisable(GetDefaultTokenUnwrapped(false));
		}

		/// <summary>
		/// Set a default Token explicitly by having called GetNewToken or GetRenewedToken, instead of relying upon the default credentials.
		/// This can be called at any time, regardless of whether a default token is already in place.
		/// If the Token is due to expire soon, then an attempt will be made to renew it.
		/// </summary>
		/// <param name="validToken">A Token_V1 object that has already been obtained via the GetNewToken or GetRenewedToken methods.</param>
		public void SetDefaultToken(PCRLE.Users.Token_V1 validToken)
		{
			if (validToken == null)
				throw new ArgumentNullException("validToken", "The Token_V1 object must not be null");

			_defaultToken = EnsureToken(validToken);
		}

		/// <summary>
		/// Set a default Token explicitly by having called GetNewToken or GetRenewedToken, instead of relying upon the default credentials.
		/// This can be called at any time, regardless of whether a default token is already in place.
		/// If the Token is due to expire soon, then an attempt will be made to renew it.
		/// </summary>
		/// <param name="serialisedToken">A Token_V1 object that has already been obtained via the GetNewToken or GetRenewedToken methods, and has been serialised to a string using the IPwsObjectSerialisable interface.</param>
		public void SetDefaultTokenFromSerialised(String serialisedToken)
		{
			if (String.IsNullOrEmpty(serialisedToken))
				throw new ArgumentNullException("serialisedToken", "The serialised Token object must not be null");

				// Fully serialised object, hence contains self links etc.
				PCRLE.Users.Token_V1 tokenV1 = new PCRLE.Users.Token_V1().FromSerialised(serialisedToken);
				_defaultToken = EnsureToken(tokenV1);
		}

		/// <summary>
		/// Set a default Token explicitly by having called GetNewToken or GetRenewedToken, instead of relying upon the default credentials.
		/// This can be called at any time, regardless of whether a default token is already in place.
		/// If the Token is due to expire soon, then an attempt will be made to renew it.
		/// The Token will be immediately retrieved from the Rest Services in order to verify it. An exception will be thrown if it is not valid.
		/// </summary>
		/// <param name="tokenString">The Token string property only, rather than fully serialised Token_V1 object.</param>
		public void SetDefaultTokenFromString(String tokenString)
		{
			if (String.IsNullOrEmpty(tokenString))
				throw new ArgumentNullException("tokenString", "The serialised Token object must not be null");

			// Only the token itself, so need to go via the root services.
			var tokens = GetRootServices().AsWrapper(this).FollowList<PCRLE.Users.Token_V1>(f => f.FindLink("tokenlist"), "filter=Token eq '" + tokenString + "'");

			if (tokens.Any() == false)
				throw new ArgumentException("No such token");

			_defaultToken = EnsureToken(tokens.First().PwsObject);
		}

		/// <summary>
		/// Get a new Token, via an implicit call to GetRootServices.
		/// This does not replace the default token which is held within the IPwsApplication instance.
		/// </summary>
		/// <param name="tokenRequest">An empty Token with the desired UserId and ExpiryTime filled in.</param>
		/// <param name="username">Username credential. This may differ from the requested Token UserId.</param>
		/// <param name="password">Password credential.</param>
		/// <returns>A new Token_V1 object.</returns>
		public PCRLE.Users.Token_V1 GetNewToken(PCRLE.Users.Token_V1 tokenRequest, string username, string password)
		{
			Link_V1 link = GetRootServices().FindLink("tokenlist");
			return _service.Post<PCRLE.Users.Token_V1>(link.Href, tokenRequest, username, password);
		}

		/// <summary>
		/// Get a renewed Token using an existing one as credentials. Makes an implicit call to GetRootServices first.
		/// This does not replace the default token which is held within the IPwsApplication instance.
		/// </summary>
		/// <param name="tokenRequest">An empty Token with the desired UserId and ExpiryTime filled in.</param>
		/// <param name="existingToken">A valid Token as authentication.</param>
		/// <returns>A new Token_V1 object.</returns>
		public PCRLE.Users.Token_V1 GetRenewedToken(PCRLE.Users.Token_V1 tokenRequest, PCRLE.Users.Token_V1 existingToken)
		{
			if (existingToken.HasSelfLink())
			{
				// Use self link, i.e. use this Token to create a new one.
				return _service.Post<PCRLE.Users.Token_V1>(existingToken.FindSelfLink().Href, tokenRequest, existingToken);
			}
			else
			{
				// Post to service token link.
				Link_V1 link = GetRootServices().FindLink("token");
				return _service.Post<PCRLE.Users.Token_V1>(link.Href, tokenRequest, existingToken);
			}
		}

		private bool HasDefaultCredentials
		{
			get
			{
				KeyValuePair<string, string> credentials = _defaultCredentials;
				return String.IsNullOrEmpty(credentials.Key) == false && String.IsNullOrEmpty(credentials.Value) == false;
			}
		}

		/// <summary>
		/// Private function to return the _defaultToken. Also used by the PwsObjectWrapper implementation.
		/// </summary>
		/// <returns></returns>
		private PCRLE.Users.Token_V1 GetDefaultTokenUnwrapped(bool allowNull)
		{
			PCRLE.Users.Token_V1 current = _defaultToken;

			// Special case: if caller permits a null Token, and the credentials have never been set, then return null.
			if (allowNull && HasDefaultCredentials == false && current == null)
				return null;

			// Normal case: regardless of whether the current Token is null, invalid, or otherwise; try to refresh it.
			_defaultToken = EnsureToken(current);
			return _defaultToken;
		}

		/// <summary>
		/// Filters the token to ensure that it is valid, and if not then obtains a new one using the default credentials.
		/// </summary>
		/// <param name="validTokenOrNull">A token or null</param>
		/// <returns>A valid token</returns>
		private PCRLE.Users.Token_V1 EnsureToken(PCRLE.Users.Token_V1 validTokenOrNull)
		{
			PCRLE.Users.Token_V1 validToken = validTokenOrNull;

			// If the token passed in is null, or expired, or due to expire, then get a new one.
			if (validToken == null || validToken.ExpiryTime < DateTime.UtcNow + __defaultTokenRenewTokenIfExpiring)
			{
				// First, try renewing using the current token if it's not null.
				if (validToken != null)
				{
					try
					{
						validToken = GetRenewedToken(CreateRequestTokenWithAccountCodes(validToken.UserId, validToken.AccountCode, validToken.SupplierCode), validToken);
					}
					catch (PwsServiceAuthenticationException)
					{
						// If default credentials are not present, then no point attempting the Second step below; just throw Authentication problem.
						KeyValuePair<string, string> credentials = _defaultCredentials;
						if (String.IsNullOrEmpty(credentials.Key) || String.IsNullOrEmpty(credentials.Value))
							throw;

						validToken = null;
					}
				}

				// Second, try renewing using the default credentials.
				if (validToken == null)
				{
					KeyValuePair<string, string> credentials = _defaultCredentials;

					if (String.IsNullOrEmpty(credentials.Key) || String.IsNullOrEmpty(credentials.Value))
						throw new InvalidOperationException("Non-empty default credentials must be provided");

					validToken = GetNewToken(CreateBasicRequestToken(credentials.Key), credentials.Key, credentials.Value);
				}
			}

			// If the token is still null, then the default credentials failed.
			if (validToken == null)
				throw new InvalidOperationException("No valid token could be obtained. Perhaps the credentials are not valid");

			if (validToken.ExpiryTime < DateTime.UtcNow)
				throw new InvalidOperationException("A token was obtained but is already out of date");

			return validToken;
		}

		/// <summary>
		/// Create a request token for a given UserId.
		/// </summary>
		private static PCRLE.Users.Token_V1 CreateBasicRequestToken(String userId)
		{
			return new PCRLE.Users.Token_V1()
			{
				UserId = userId,
				ExpiryTime = DateTime.UtcNow + __defaultTokenExpiryTime
			};
		}

		/// <summary>
		/// Create a request token for a given UserId, with the option to specify customer and supplier account codes.
		/// </summary>
		private static PCRLE.Users.Token_V1 CreateRequestTokenWithAccountCodes(String userId, String accountCode, String supplierCode)
		{
			return new PCRLE.Users.Token_V1()
			{
				UserId = userId,
				ExpiryTime = DateTime.UtcNow + __defaultTokenExpiryTime,
			    AccountCode = accountCode,
				SupplierCode = supplierCode
			};
		}
		#endregion

		#region Performing operations on objects. Most Clients can ignore this.
		/// <summary>
		/// Because several different users might be acting upon objects at the same time, sometimes update operations
		/// (put/post/delete) may throw an exception due to another user having made changes more recently.
		/// In order to ensure that the user is transacting the most recent version of any given object, call this method
		/// which will re-retrieve it using the "self" link within its Links array.
		/// NB: The client application is responsible for performing any applicable merge operation between the old and
		/// new version.
		/// If the object has no "self" link, then no action is taken and the supplied pwsObject is returned back again.
		/// The default Token is used as the credentials for this operation.
		/// </summary>
		/// <typeparam name="T">The PWS Object type being re-retrieved.</typeparam>
		/// <param name="pwsObject">The PWS Object.</param>
		/// <returns>The PWS Object representing its most recent state.</returns>
		public T Refresh<T>(T pwsObject) where T : class, IPwsObject_V1
		{
			// Quick exit if no self link. Return same object back.
			if (pwsObject.HasSelfLink() == false)
				return pwsObject;

			Link_V1 selfLink = pwsObject.FindSelfLink();
			PCRLE.Users.Token_V1 token = GetDefaultTokenUnwrapped(true);

			return _service.Get<T>(selfLink.Href, token);
		}
		#endregion

		#region Wrapping objects into a more convenient form. Most Clients can ignore this.
		/// <summary>
		/// Creates a wrapper around a PWS Object, which becomes a simple starting point for REST operations.
		/// The wrapper can be used as a more convenient way to follow links to contained objects, and also as a more convenient way to Refresh the object.
		/// The default Token is used as the credentials for any operations used on the IPwsObjectWrapper.
		/// </summary>
		/// <typeparam name="T">Object type which contains the link to follow, e.g. Customer_V1</typeparam>
		/// <param name="pwsObject">The PWS Object to wrap.</param>
		/// <returns>IPwsApplicationFollow instance against the type of PWS Object.</returns>
		public IPwsObjectWrapper<T> Wrap<T>(T pwsObject) where T : class, IPwsObject_V1
		{
			return new PwsObjectWrapper<T>(this, pwsObject);
		}

		/// <summary>
		/// Creates a wrapper around a PWS Object, which becomes a simple starting point for REST operations.
		/// The wrapper can be used as a more convenient way to follow links to contained objects, and also as a more convenient way to Refresh the object.
		/// The default Token is used as the credentials for any operations used on the IPwsObjectWrapper, unless it is null.
		/// This overload returns an extended wrapper object which is serialisable.
		/// </summary>
		/// <typeparam name="T">Object type which contains the link to follow, e.g. Customer_V1</typeparam>
		/// <param name="pwsObject">The PWS Object to wrap.</param>
		/// <returns>IPwsObjectWrapper instance against the type of PWS Object.</returns>
		public IPwsObjectWrapperSerialisable<T> WrapSerialisable<T>(T pwsObject) where T : class, IPwsObjectSerialisable_V1<T>
		{
			return new PwsObjectWrapperSerialisable<T>(this, pwsObject);
		}
		#endregion

		#region PwsObjectWrapper implementation
		[DebuggerDisplay("{PwsObject}")]
		private class PwsObjectWrapper<T> : IPwsObjectWrapper<T> where T : class, IPwsObject_V1
		{
			private readonly PwsApplication _pwsApplication;
			private T _pwsObject;

			/// <summary>
			/// Create an object wrapper.
			/// </summary>
			internal PwsObjectWrapper(PwsApplication pwsApplication, T pwsObject)
			{
				if (pwsApplication == null)
					throw new ArgumentNullException("pwsApplication", "The PwsObjectWrapper must be instantiated with a PwsApplication object");

				if (pwsObject == null)
					throw new ArgumentNullException("pwsObject", "The PwsObjectWrapper must be instantiated with a pwsObject");

				_pwsApplication = pwsApplication;
				_pwsObject = pwsObject;
			}

			#region Helper functions.

			/// <summary>
			/// Return the authentication token being used by this object.
			/// </summary>
			private PCRLE.Users.Token_V1 GetToken(bool allowNull)
			{
				return _pwsApplication.GetDefaultTokenUnwrapped(allowNull);
			}

			/// <summary>
			/// Creates another wrapper object. Handles passing through the token (if applicable).
			/// </summary>
			private IPwsObjectWrapper<U> Wrap<U>(U pwsObject) where U : class, IPwsObject_V1
			{
				return _pwsApplication.Wrap(pwsObject);
			}

			/// <summary>
			/// Take some OData style of query parameters and construct an OData URI parameter list.
			/// </summary>
			/// <returns>Query parameters without the ? or £, e.g. filter=This Eq 'That'</returns>
			private String ConstructODataString(String[] query)
			{
				// Quick exit if no query strings.
				if (query.Length == 0 || query.All(f => String.IsNullOrEmpty(f)))
					return String.Empty;

				String result = "?" + String.Join("&", query.Select(f => "$" + f).ToArray());
				return result;
			}

			#endregion

			#region IPwsApplicationObjectWrapper<T> Members

			public IPwsObjectWrapper<U> Follow<U>(Func<T, Link_V1> func) where U : class, IPwsObject_V1
			{
				PCRLE.Users.Token_V1 token = GetToken(true);

				Link_V1 link = func(_pwsObject);
				if (link == null)
					throw new PwsServiceNullRelationshipException(_pwsObject, typeof(U));

				U nextObject = _pwsApplication.PwsService.Get<U>(link.Href, token);

				IPwsObjectWrapper<U> nextWrapper = Wrap(nextObject);
				return nextWrapper;
			}

			public FileInfo GetBinaryFile(Func<T, Link_V1> func)
			{
				PCRLE.Users.Token_V1 token = GetToken(false);
				return _pwsApplication.PwsService.GetBinaryFile(func(_pwsObject).Href, func(_pwsObject).MediaType, token);
			}

			public IList<IPwsObjectWrapper<U>> FollowList<U>(Func<T, Link_V1> func, String[] query) where U : class, IPwsObject_V1
			{
				PCRLE.Users.Token_V1 token = GetToken(true);

				Uri uri = new Uri(func(_pwsObject).Href.ToString() + ConstructODataString(query));
				return _pwsApplication.PwsService.GetList<U>(uri, token).Select(f => (IPwsObjectWrapper<U>)new PwsObjectWrapper<U>(_pwsApplication, f)).ToList();
			}

			public IList<IPwsObjectWrapper<U>> FollowList<U>(Func<T, Link_V1> func) where U : class, IPwsObject_V1
			{
				PCRLE.Users.Token_V1 token = GetToken(true);

				return _pwsApplication.PwsService.GetList<U>(func(_pwsObject).Href, token).Select(f => (IPwsObjectWrapper<U>)new PwsObjectWrapper<U>(_pwsApplication, f)).ToList();
			}

			public IListPage<U> FollowListPage<U>(Func<T, Link_V1> func, String[] query) where U : class, IPwsObject_V1
			{
				PCRLE.Users.Token_V1 token = GetToken(true);

				Uri uri = new Uri(func(_pwsObject).Href.ToString() + ConstructODataString(query));

				Uri previousPage;
				Uri nextPage;
				IEnumerable<U> objects = _pwsApplication.PwsService.GetList<U>(uri, token, out previousPage, out nextPage);

				return new ListPage<U>(_pwsApplication, objects, previousPage, nextPage);
			}

			public IListPage<U> FollowListPage<U>(Func<T, Link_V1> func) where U : class, IPwsObject_V1
			{
				PCRLE.Users.Token_V1 token = GetToken(true);

				Uri previousPage;
				Uri nextPage;
				IEnumerable<U> objects = _pwsApplication.PwsService.GetList<U>(func(_pwsObject).Href, token, out previousPage, out nextPage);

				return new ListPage<U>(_pwsApplication, objects, previousPage, nextPage);
			}

			public IPwsObjectWrapper<U> Post<U>(Func<T, Link_V1> func, U sendObject) where U : class, IPwsObject_V1
			{
				if (sendObject == null)
					throw new ArgumentNullException("sendObject", "Post must be called with a valid IPwsObject_V1");

				U postedResult = _pwsApplication.PwsService.Post<U>(func(_pwsObject).Href, sendObject, GetToken(false));
				_pwsObject = _pwsApplication.Refresh(_pwsObject);

				IPwsObjectWrapper<U> nextWrapper = Wrap(postedResult);
				return nextWrapper;
			}

			public IPwsObjectWrapper<U> PostAnonymously<U>(Func<T, Link_V1> func, U sendObject) where U : class, IPwsObject_V1
			{
				if (sendObject == null)
					throw new ArgumentNullException("sendObject", "Post must be called with a valid IPwsObject_V1");

				U postedResult = _pwsApplication.PwsService.Post<U>(func(_pwsObject).Href, sendObject, GetToken(true));
				_pwsObject = _pwsApplication.Refresh(_pwsObject);

				IPwsObjectWrapper<U> nextWrapper = Wrap(postedResult);
				return nextWrapper;
			}

			public IPwsObjectWrapper<U> PostBinaryFile<U>(Func<T, Link_V1> func, Stream stream, String mediaType) where U : class, IPwsObject_V1
			{
				if (stream == null)
					throw new ArgumentNullException("stream", "PostBinaryFile must be called with a non-null Stream");

				U postedResult = _pwsApplication.PwsService.PostBinaryFile<U>(func(_pwsObject).Href, stream, mediaType, GetToken(false));

				IPwsObjectWrapper<U> nextWrapper = Wrap(postedResult);
				return nextWrapper;
			}

			public IPwsObjectWrapper<U> Put<U>(Func<T, Link_V1> func, U sendObject) where U : class, IPwsObject_V1
			{
				if (sendObject == null)
					throw new ArgumentNullException("sendObject", "Put must be called with a valid IPwsObject_V1");

				U putResult = _pwsApplication.PwsService.Put<U>(func(_pwsObject).Href, sendObject, GetToken(false));
				_pwsObject = _pwsApplication.Refresh(_pwsObject);

				IPwsObjectWrapper<U> nextWrapper = Wrap(putResult);
				return nextWrapper;
			}

			public void Delete<U>(Func<T, Link_V1> func, U sendObject) where U : class, IPwsObject_V1
			{
				if (sendObject == null)
					throw new ArgumentNullException("sendObject", "Put must be called with a valid IPwsObject_V1");

				_pwsApplication.PwsService.Delete<U>(func(_pwsObject).Href, sendObject, GetToken(false));
			}

			public void DeleteSelf()
			{
				_pwsApplication.PwsService.Delete(_pwsObject.FindSelfLink().Href, _pwsObject, GetToken(false));
			}

			public IPwsObjectWrapper<T> PutSelf()
			{
				T refreshedObject = _pwsApplication.PwsService.Put(_pwsObject.FindSelfLink().Href, _pwsObject, GetToken(false));
				IPwsObjectWrapper<T> refreshedWrapper = Wrap(refreshedObject);
				return refreshedWrapper;
			}

			public IPwsObjectWrapper<T> Refresh()
			{
				T refreshedObject = _pwsApplication.Refresh(_pwsObject);
				IPwsObjectWrapper<T> refreshedWrapper = Wrap(refreshedObject);
				return refreshedWrapper;
			}

			public T PwsObject
			{
				get { return _pwsObject; }
			}

			#endregion

			public override string ToString()
			{
				return _pwsObject.ToString();
			}

			#region IListPage implementation.
			[DebuggerDisplay("{ThisPage.Count} items, PreviousPage={HasPreviousPage}, NextPage={HasNextPage}")]
			private class ListPage<U> : IListPage<U> where U : class, IPwsObject_V1
			{
				private readonly PwsApplication _pwsApplication;
				private readonly IList<IPwsObjectWrapper<U>> _pwsObjects;
				private readonly Uri _previousPage;
				private readonly Uri _nextPage;

				/// <summary>
				/// Create a ListPage from an already retrieved set of objects.
				/// </summary>
				internal ListPage(PwsApplication pwsApplication, IEnumerable<U> pwsObjects, Uri previousPage, Uri nextPage)
				{
					if (pwsApplication == null)
						throw new ArgumentNullException("pwsApplication");

					if (pwsObjects == null)
						throw new ArgumentNullException("pwsObjects");

					_pwsApplication = pwsApplication;
					_pwsObjects = pwsObjects.Select(f => (IPwsObjectWrapper<U>)new PwsObjectWrapper<U>(pwsApplication, f)).ToList();
					_previousPage = previousPage;
					_nextPage = nextPage;
				}

				/// <summary>
				/// Return the authentication token being used by this object.
				/// </summary>
				private PCRLE.Users.Token_V1 GetToken(bool allowNull)
				{
					return _pwsApplication.GetDefaultTokenUnwrapped(allowNull);
				}

				#region IListPage<U> Members

				public IList<IPwsObjectWrapper<U>> ThisPage
				{
					get { return _pwsObjects; }
				}

				public IListPage<U> GetPreviousPage()
				{
					if (HasPreviousPage == false)
						return null;

					PCRLE.Users.Token_V1 token = GetToken(true);

					Uri newPreviousPage;
					Uri newNextPage;
					IEnumerable<U> newObjects = _pwsApplication.PwsService.GetList<U>(_previousPage, token, out newPreviousPage, out newNextPage);

					return new ListPage<U>(_pwsApplication, newObjects, newPreviousPage, newNextPage);
				}

				public IListPage<U> GetNextPage()
				{
					if (HasNextPage == false)
						return null;

					PCRLE.Users.Token_V1 token = GetToken(true);

					Uri newPreviousPage;
					Uri newNextPage;
					IEnumerable<U> newObjects = _pwsApplication.PwsService.GetList<U>(_nextPage, token, out newPreviousPage, out newNextPage);

					return new ListPage<U>(_pwsApplication, newObjects, newPreviousPage, newNextPage);
				}

				public bool HasPreviousPage
				{
					get { return _previousPage != null; }
				}

				public bool HasNextPage
				{
					get { return _nextPage != null; }
				}

				#endregion
			}
			#endregion
		}

		[DebuggerDisplay("{PwsObject}")]
		private class PwsObjectWrapperSerialisable<T> : PwsObjectWrapper<T>, IPwsObjectWrapperSerialisable<T> where T : class, IPwsObjectSerialisable_V1<T>
		{
			internal PwsObjectWrapperSerialisable(PwsApplication pwsApplication, T pwsObject)
				: base(pwsApplication, pwsObject)
			{
			}

			public string Serialise()
			{
				return PwsObject.AsSerialised();
			}
		}
		#endregion
	}

	internal class TokenRequest_V1 : ITokenRequest_V1
	{
		private readonly PCRLE.Users.Token_V1 _newTokenRequest;
		private readonly IPwsObjectWrapper<PCRLE.Users.Token_V1> _existingToken;
		private readonly Func<PCRLE.Users.Token_V1, IPwsApplication> _pwsApplicationFunction;

		private PCRLE.Users.Token_V1 CloneToken()
		{
			// NB Keep this synchronised with PwsApplication.CreateRequestTokenWithAccountCodes
			return new PCRLE.Users.Token_V1()
			{
				UserId = _newTokenRequest.UserId,
				ExpiryTime = _newTokenRequest.ExpiryTime,
				AccountCode = _newTokenRequest.AccountCode,
				SupplierCode = _newTokenRequest.SupplierCode
			};
		}

		public TokenRequest_V1(IPwsObjectWrapper<PCRLE.Users.Token_V1> existingToken, Func<PCRLE.Users.Token_V1, IPwsApplication> pwsApplicationFunction, PCRLE.Users.Token_V1 newTokenRequest)
		{
			_existingToken = existingToken;
			_pwsApplicationFunction = pwsApplicationFunction;
			_newTokenRequest = newTokenRequest;
		}

		public IPwsObjectWrapperSerialisable<PCRLE.Users.Token_V1> GetRenewedToken()
		{
			IPwsApplication pws = _pwsApplicationFunction(_existingToken.PwsObject);
			PCRLE.Users.Token_V1 newToken = pws.GetRenewedToken(_newTokenRequest, _existingToken.PwsObject);
			pws.SetDefaultToken(newToken);
			return pws.WrapSerialisable(newToken);
		}

		public ITokenRequest_V1 WithAccountOverride(string accountCode)
		{
			if (String.IsNullOrEmpty(accountCode)) throw new ArgumentNullException("accountCode", "Please provide a non-null and non-empty account code, or use the WithoutOverrides method if intending to revert to the default account code");

			PCRLE.Users.Token_V1 newToken = CloneToken();
			newToken.AccountCode = accountCode;

			return new TokenRequest_V1(_existingToken, _pwsApplicationFunction, newToken);
		}

		public ITokenRequest_V1 WithSupplierOverride(string supplierCode)
		{
			if (String.IsNullOrEmpty(supplierCode)) throw new ArgumentNullException("supplierCode", "Please provide a non-null and non-empty supplier code, or use the WithoutOverrides method if intending to revert to the default supplier code");

			PCRLE.Users.Token_V1 newToken = CloneToken();
			newToken.SupplierCode = supplierCode;

			return new TokenRequest_V1(_existingToken, _pwsApplicationFunction, newToken);
		}

		public ITokenRequest_V1 WithoutOverrides()
		{
			PCRLE.Users.Token_V1 newToken = CloneToken();
			newToken.AccountCode = String.Empty;
			newToken.SupplierCode = String.Empty;

			return new TokenRequest_V1(_existingToken, _pwsApplicationFunction, newToken);
		}

		public ITokenRequest_V1 WithExpiryTime(DateTime expiry)
		{
			PCRLE.Users.Token_V1 newToken = CloneToken();
			newToken.ExpiryTime = expiry;

			return new TokenRequest_V1(_existingToken, _pwsApplicationFunction, newToken);
		}
	}
}
